package gui;

import collector.Chain;
import collector.Chunk;
import collector.Configuration;
import collector.FileObj;
import parser.JavaRecognizer;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.TableColumn;
import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Map;

/**
 * @author dotekien
 *         Date: Mar 27, 2006
 *         Time: 10:51:04 PM
 */
public class GUI extends JFrame implements Runnable {

    //Jthis this;
    Container mainPane;

    JToolBar toolbar;
    JButton chooseJaveSource;
    JButton redefineEquate;
    JButton help;
    JButton about;

    JSplitPane resultsWindow;
    JSplitPane codeWindow;
    JTabbedPane statisticWindow;
    JPanel chainWindow;
    JPanel fileWindow;

    JLabel leftStatus;
    JScrollPane leftScroll;
    JTextArea fileText;

    Navigator leftNavigator;
//    JPanel panel;
//    JLabel label;
//    SameComboBox comboBox;
//    JButton next;
//    JButton previous;

    JLabel chunkStatus;
    JScrollPane chunkScroll;
    JTextArea chunkText;

    Navigator chunkNavigator;
//    JPanel chunkNavigator;
//    JLabel chunkLabel;
//    SameComboBox chunkList;
//    JButton chunkNext;
//    JButton chunkPrevious;

    JPanel statisticViewPane;
    JScrollPane chainViewScroll;
    JScrollPane fileViewScroll;

    JLabel statistic;
    SameTable chainTable;
    SameTable fileTable;
    private final Configuration configuration = new Configuration(true);
    private static final int FIRST_COL_WIDTH = 500;
    private static final String TITLE = "Visual Same - The tool for viewing code duplication";

    public GUI() {
        configuration.minLengthForChunk = 3;

        toolbar = new JToolBar();
        chooseJaveSource = new JButton();
        redefineEquate = new JButton();
        help = new JButton();
        about = new JButton();

        resultsWindow = new JSplitPane();
        codeWindow = new JSplitPane();
        statisticWindow = new JTabbedPane();

        chainWindow = new JPanel();
        fileWindow = new JPanel();

        leftStatus = new JLabel();
        leftScroll = new JScrollPane();
        fileText = new JTextArea();

        leftNavigator = new Navigator();

        chunkStatus = new JLabel();
        chunkScroll = new JScrollPane();
        chunkText = new JTextArea();

        chunkNavigator = new Navigator();

        statisticViewPane = new JPanel();
        statistic = new JLabel();

        chainViewScroll = new JScrollPane();
        chainTable = new SameTable();

        fileViewScroll = new JScrollPane();
        fileTable = new SameTable();
    }

    public void run() {
        createAndShowGui();
    }

    SameProgressMonitor pbar;
    LongTask task;
    Timer timer;
    ArrayList<FileObj> files;

    public void action(File[] sf) {
        pbar = new SameProgressMonitor(this, "", "Initializing...", 0, 100);
        setEnabled(false);

        files = new ArrayList<FileObj>();
        for (File f : sf) {
            getJavaSource(f);
        }
        if (files.size() > 0) {
            task = new LongTask(configuration, files);
            timer = new Timer(FIRST_COL_WIDTH, new TimerListener(this));

            // Fire a timer every once in a while to update the progress.
            task.go();
            timer.start();
        } else {
            setEnabled(true);
            pbar.close();
            //show dialog box
            JOptionPane.showMessageDialog(this, "Your selected folders contain no java code file",
                    "", JOptionPane.ERROR_MESSAGE);
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    class TimerListener implements ActionListener {
        GUI frame;

        public TimerListener(GUI frame) {
            this.frame = frame;
        }

        public void actionPerformed(ActionEvent evt) {
            //gui.repaint();
            pbar.setProgress(task.getCurrent());
            String s = task.getMessage();
            if (s != null) {
                pbar.setNote(s);
            }
            if (pbar.isCanceled() || task.isDone() || task.isCanceled()) {
                task.stop();
                //Toolkit.getDefaultToolkit().beep();
                if (task.isDone() || pbar.isCanceled()) {
                    frame.setEnabled(true);
                    pbar.close();
                    timer.stop();
                    if (task.isDone()) {
                        //data now is ready
                        updateStatistic();
                        updateView(task.ctm, chainTable, ChainTableModel.TOOL_TIPS);
                        updateView(task.ftm, fileTable, FileTableModel.TOOL_TIPS);

                        if (getActiveTab() == 1) {  //Chainview
                            updateChainToLeftNavi();
                        } else if (getActiveTab() == 2) { //File view
                            updateFileToLeftNavi();
                        }
                    }
                }
            }
        }
    }

    private void updateView(SameTableModel stm, SameTable table, String[] colToolTips) {
        TableSorter sorter = new TableSorter(stm); //ADDED THIS
        table.setModel(sorter);             //NEW
        sorter.setColumnComparator(Object.class, TableSorter.LEXICAL_COMPARATOR);
        sorter.setColumnComparator(Number.class, TableSorter.COMPARABLE_COMAPRATOR);

        table.setcolumnToolTips(colToolTips);
        sorter.setTableHeader(table.getTableHeader()); //ADDED T

        table.setDefaultRenderer(Object.class, new SameCellRenderer(Object.class));
        table.setDefaultRenderer(Number.class, new SameCellRenderer(Number.class));

        TableColumn col = table.getColumnModel().getColumn(0);
        col.setPreferredWidth(15);

        col = table.getColumnModel().getColumn(1);
        col.setPreferredWidth(FIRST_COL_WIDTH);

        table.addMouseListener(new SameTableMouseListener(leftNavigator.comboBox));
        table.updateUI();
    }

    SameComboBoxSelection leftItemListener;

    private void updateLeftNavigator(ArrayList data, String label,
                                     String toolTipLabel, String toolTipListBox,
                                     String toolTipN, String toolTipP) {

        setupLabel(leftNavigator.label, label, toolTipLabel);
        leftNavigator.next.setToolTipText(toolTipN);
        leftNavigator.previous.setToolTipText(toolTipP);

        if (data.size() > 0) {
            if (leftItemListener != null) {
                leftNavigator.comboBox.removeItemListener(leftItemListener);
            }
            leftItemListener = new SameComboBoxSelection(this);
            updateCombo(leftNavigator.comboBox, leftItemListener, data, toolTipListBox);
        }
    }

    SameComboBoxSelection rightItemListener;

    public void updateRighCombo(ArrayList data) {
        if (rightItemListener != null) {
            chunkNavigator.comboBox.removeItemListener(rightItemListener);
        }
        rightItemListener = new SameComboBoxSelection(this);
        updateCombo(chunkNavigator.comboBox, rightItemListener, data, "Chunk list");
    }

    private static void updateCombo(SameComboBox combo, SameComboBoxSelection it, ArrayList<?> data, String toolTipListBox) {

        combo.addItemListener(it);
        combo.setModel(new SameComboBoxModel(data));
        combo.setToolTipText(toolTipListBox);

        JTextComponent editor = (JTextComponent) combo.getEditor().getEditorComponent();
        editor.setDocument(new SameAutoComplete(combo));
    }

    private void updateChainToLeftNavi() {
        updateLeftNavigator(task.chains, "Chains",
                "<html>To view a chain, please choose a list item in list box or press the next or the previous button.<br>" +
                        "A chain is defined a list of chunks that are continuous statements in a file.<br>" +
                        " A chunk can be duplicated in one file or among files</html>",
                "Chain list", "Next chain", "Previous chain");
    }

    private void updateFileToLeftNavi() {
        updateLeftNavigator(task.containDupFiles, "Files",
                "To view a file, please choose a list item in list box or press the next or the previous button",
                "File list", "Next file", "Previous file");
    }

    private int getActiveTab() {
        return statisticWindow.getSelectedIndex();
    }

    public void updateTextWindow(Chunk chunk) {
        updateStatus(chunk);
        String code = chunk.getChain().getStandardizeForm();
        updateChunkText(code);
        String fileCode = getFileContent(chunk.getFile().getFile());
        //need to plus 1 to cover the last one; read javax.swing.text.Document to know why :)
        updateFileText(fileCode, chunk.getIndexStart(), chunk.getIndexEnd() + 1);
    }

    private void updateChunkText(String code) {
        chunkText.setText(code);
        chunkText.setCaretPosition(0);
        chunkText.repaint();
    }

    private void updateFileText(String code, int indexStart, int indexEnd) {

        fileText.setText(code);
        Highlighter h = fileText.getHighlighter();
        try {
            h.addHighlight(indexStart, indexEnd, new SameHighlightPainter(HIGHLIGHT_COLOR));
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
        fileText.setCaretPosition(indexStart);
        scrollCenterToPosition(fileText, indexStart);
        fileText.repaint();
    }

    public void scrollCenterToPosition(JTextArea textWindow, int position) {
        try {
            Rectangle r = textWindow.modelToView(position);
            if (r != null) {
                Rectangle vis = textWindow.getVisibleRect();
                r.y += vis.height;
                r.height = vis.height;
                textWindow.scrollRectToVisible(r);
            }
        } catch (BadLocationException ble) {
            // ignore
        }
    }

    private static final int CHAR_ARRAY_LENGHT = 8192;

    public String getFileContent(File file) {
        //make char array
        StringBuffer fcontent = new StringBuffer();
        try {
            BufferedReader readf = new BufferedReader(new FileReader(file));
            int noc;
            while (true) {
                char[] chars = new char[CHAR_ARRAY_LENGHT];
                noc = readf.read(chars, 0, CHAR_ARRAY_LENGHT);
                if (noc == -1) {
                    break;
                } else if (noc == CHAR_ARRAY_LENGHT) {
                    fcontent.append(chars);
                } else {
                    for (int i = 0; i < noc; i++) {
                        fcontent.append(chars[i]);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fcontent.toString().intern();
    }

    public void updateStatistic() {
        int nods = 0;
        for (FileObj f : task.files) {
            nods += f.getNumberOfWarningStatement();
        }
        int nos = task.getCollector().getProjectNumOfStatement();
        DecimalFormat df = new DecimalFormat("###.##");
        String pdc = df.format((100.0 * nods) / nos);

        StringBuffer sb = new StringBuffer();
        sb.append("<html>");
        sb.append("<br>&nbsp;&nbsp;The percentage of duplicated codes:&nbsp;&nbsp;" + "<b>").append(pdc).append("%</b><br>");

        sb.append("<br>&nbsp;&nbsp;The number of scanned files:&nbsp;&nbsp;" + "<b>").append(task.files.size()).append("</b>");
        sb.append("<br>&nbsp;&nbsp;The number of containing-duplication-chunk files:&nbsp;&nbsp;" + "<b>").append(task.containDupFiles.size()).append("</b>");
        sb.append("<br>&nbsp;&nbsp;The number of physic code lines (without comments):&nbsp;&nbsp;" + "<b>").append(task.getCollector().getNumberOfPhysicalLines()).append("</b>");
        sb.append("<br>&nbsp;&nbsp;The number of statements:&nbsp;&nbsp;" + "<b>").append(nos).append("</b>");
        sb.append("<br>&nbsp;&nbsp;The number of duplicated statements:&nbsp;&nbsp;" + "<b>").append(nods).append("</b>" + "&nbsp;&nbsp;(One statement can be counted more than one time when it belongs to more than one chunk)<br>");

        sb.append("<br>&nbsp;&nbsp;The minimum statements in one chunk:&nbsp;&nbsp;" + "<b>").append(configuration.minLengthForChunk).append("</b>");

        boolean flag = false;
        StringBuffer tmp = new StringBuffer();
        for (Map.Entry<Integer, Boolean> entry : configuration.getEquateTable().entrySet()) {
            if (entry.getValue()) {
                String item = JavaRecognizer._tokenNames[entry.getKey()];
                tmp.append("<b>").append(item).append("</b>,&nbsp;");
                flag = true;
            }
        }

        if (flag) {
            sb.append("<br>&nbsp;&nbsp;Fuzzy match with equate set:&nbsp;");
            tmp.delete(tmp.length() - ",&nbsp;".length() - 1, tmp.length()); //remove ", "
            sb.append(tmp.toString().intern());
        }
        sb.append("</html>");

        statistic.setText(sb.toString().intern());

        statistic.repaint();
    }

    public void updateStatus(Chunk chunk) {
        if (leftNavigator.comboBox.getSelectedItem() instanceof Chain) {
            leftStatus.setText(" " + chunk.getChain().toStringForTable());
        } else {
            leftStatus.setText(" " + "info: " +
                    chunk.getChain().getNumberOfStatement() + " statements in this chunk.");
        }
        chunkStatus.setText(" " + chunk.getFile().getPath());
    }

    /*
    * Recursion explore the folder to get the java source code
    */
    private void getJavaSource(File f) {
        //If this is a directory, walk each file/dir in that directory
        if (f.isDirectory()) {
            String files[] = f.list();
            for (String file : files) getJavaSource(new File(f, file));
        }
        // otherwise, if this is a java file, parse it!
        else if (f.getName().length() > 5 && f.getName().endsWith(".java")) {
            receiveFile(f);
        }
    }

    /*
    * Make FileObj
    */
    private void receiveFile(File f) {
        //make file object
        FileObj fo = new FileObj(f);
        //put to file array
        files.add(fo);
    }

    public void createAndShowGui() {
        setupFrame();

        mainPane = getContentPane();
        mainPane.setLayout(new BorderLayout());
        mainPane.setPreferredSize(new Dimension(1017, 740));

        //do something here
        setupToolbar();
        setupResultsWindow();

        pack();
        setVisible(true);
    }

    private void setupFrame() {
        JFrame.setDefaultLookAndFeelDecorated(true);
        setTitle(TITLE);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }


    private void setupToolbar() {
        //Four buttons: "open source code folder", "define equate measurement", "about us", "help"
        toolbarInit();
        chooseButtonSetup();

        toolbar.add(Box.createHorizontalGlue());
        helpLabelSetup();
        toolbar.add(Box.createRigidArea(new Dimension(20, 30)));
        aboutLabelSetup();

        mainPane.add(toolbar, BorderLayout.PAGE_START);
    }

    private void aboutLabelSetup() {
        about.setText("About Us");
        about.addActionListener(new OpenHtmlPage(this, "About Us", "/pages/about.htm", new Dimension(600, 400)));
        toolbar.add(about);
    }

    private void helpLabelSetup() {
        help.setText("Help");
        help.addActionListener(new OpenHtmlPage(this, "Help", "/pages/help.htm", new Dimension(600, 700)));
        toolbar.add(help);
    }

    private void chooseButtonSetup() {
        chooseJaveSource.setText("Choose java sources");
        chooseJaveSource.addActionListener(new OpenFileAction(this,
                "Choose JAVA source code folders or files (use CTRL for a multiple selection)"));
        toolbar.add(chooseJaveSource);
    }

    private void toolbarInit() {
        toolbar.setPreferredSize(new Dimension(-1, 30));
        toolbar.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
        toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS));
    }

    private void setupResultsWindow() {
        resultsWindow.setBorder(BorderFactory.createEmptyBorder());
        resultsWindow.setOneTouchExpandable(true);
        resultsWindow.setOrientation(JSplitPane.VERTICAL_SPLIT);
        resultsWindow.setDividerLocation(480);
        setupCodeWindow();  //upper window
        setupStatisticWindow(); //lower window

        mainPane.add(resultsWindow, BorderLayout.CENTER);
    }


    private void setupCodeWindow() {
        codeWindow.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
        codeWindow.setOneTouchExpandable(true);
        codeWindow.setDividerLocation(350);
        setupLeftWindow(); //left upper window
        setupRightWindow();  //right upper window

        resultsWindow.setLeftComponent(codeWindow);
    }

    private void setupLeftWindow() {
        chainWindow.setLayout(new BorderLayout());

        setupStatus(chainWindow, leftStatus);
        setupTextScroll(chainWindow, leftScroll, chunkText);

        leftNavigator.comboBox.setup("", null);
        setupButton(leftNavigator.previous, "navigation/Back16.gif", "",
                new NavigatorActionListener(leftNavigator.comboBox, 0));
        setupButton(leftNavigator.next, "navigation/Forward16.gif", "",
                new NavigatorActionListener(leftNavigator.comboBox, 1));

        leftNavigator.setup(chainWindow, true);

        codeWindow.setLeftComponent(chainWindow);
    }

    private static void setupStatus(JPanel panel, JLabel status) {
        status.setOpaque(true);
        status.setBackground(BG_COLOR);
        status.setFont(new Font("Tahoma", Font.BOLD, 10));
        status.setForeground(HIGHLIGHT_COLOR);

        panel.add(status, BorderLayout.PAGE_START);
    }

    private static void setupTextScroll(JPanel panel, JScrollPane scrollPane, JTextArea textArea) {
        //text area
        setupText(textArea);
        scrollPane.setViewportView(textArea);

        //vertical scroll bar
        int h = scrollPane.getVerticalScrollBar().getPreferredSize().height;
        scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(11, h));

        //horizontal scroll bar
        int w = scrollPane.getHorizontalScrollBar().getPreferredSize().width;
        scrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(w, 12));

        panel.add(scrollPane, BorderLayout.CENTER);
    }


    private static void setupText(JTextArea text) {
        text.setBackground(TEXT_BG_COLOR);
        text.setBorder(BorderFactory.createEmptyBorder());
        text.setOpaque(true);
        text.setFont(new Font("Courier", Font.PLAIN, 12));
        text.setTabSize(4);
        text.setEditable(false);
    }

    private static void setupLabel(JLabel label, String text, String toolTip) {
        label.setText(text);
        label.setToolTipText(toolTip);
    }


    private void setupButton(JButton button, String imgLocation, String toolTip, ActionListener act) {
        button.setToolTipText(toolTip);
        URL imageURL = getClass().getClassLoader().getResource(imgLocation);
        button.setIcon(new ImageIcon(imageURL, toolTip));
        button.setOpaque(false);
        button.setFocusable(true);
        button.setMargin(new Insets(0, 0, 0, 0));
        button.addActionListener(act);
    }

    private void setupRightWindow() {
        fileWindow.setLayout(new BorderLayout());

        setupStatus(fileWindow, chunkStatus);
        setupTextScroll(fileWindow, chunkScroll, fileText);

        setupLabel(chunkNavigator.label, " Chunks ", "To view a chunk, please choose a list item in list box or press the next or the previous button");
        chunkNavigator.comboBox.setup("The list of chunks", null);
        setupButton(chunkNavigator.previous, "navigation/Back16.gif", "Previous chunk",
                new NavigatorActionListener(chunkNavigator.comboBox, 0));
        setupButton(chunkNavigator.next, "navigation/Forward16.gif", "Next chunk",
                new NavigatorActionListener(chunkNavigator.comboBox, 1));
        chunkNavigator.setup(fileWindow, false);

        codeWindow.setRightComponent(fileWindow);
    }

    private void setupStatisticWindow() {
        setupStatisticView();
        setupChainview();
        setupFileView();

        statisticWindow.addTab("Statistic", statisticViewPane);
        statisticWindow.addTab("Chain View", chainViewScroll);
        statisticWindow.addTab("File View", fileViewScroll);
        statisticWindow.addChangeListener(new ChangeTab());
        resultsWindow.setRightComponent(statisticWindow);
    }

    private class ChangeTab implements ChangeListener {
        public void stateChanged(ChangeEvent e) {
            JTabbedPane source = (JTabbedPane) e.getSource();
            if (task != null && task.isDone()) {
                switch (source.getSelectedIndex()) {
                    case 1: //Chain view
                        updateChainToLeftNavi();
                        break;
                    case 2: //File view
                        updateFileToLeftNavi();
                        break;
                    default:
                }
            }
        }
    }


    private void setupStatisticView() {
        statistic.setFont(new Font("Tahoma", Font.PLAIN, 13));
        statistic.setBackground(TEXT_BG_COLOR);
        statistic.setOpaque(false);

        statisticViewPane.setBackground(TEXT_BG_COLOR);
        statisticViewPane.setLayout(new BorderLayout());
        statisticViewPane.add(statistic, BorderLayout.PAGE_START);
    }

    private void setupChainview() {
        chainViewScroll.setViewportView(chainTable);
    }

    private void setupFileView() {
        fileViewScroll.setViewportView(fileTable);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new GUI());
    }

    private static final Color TEXT_BG_COLOR = new Color(255, 255, 255);
    private static final Color BG_COLOR = new Color(240, 240, 240);
    private static final Color HIGHLIGHT_COLOR = new Color(80, 150, 80);
}
